package com.yxplearn.mybatisplus.util;

/**
 * SnowflakeId ID生成器
 *
 * @author xiezuojie
 */
public class IDGenerator {
    /**
     * Twitter_Snowflake<br>
     * SnowFlake的结构如下(每部分用-分开):<br>
     * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
     * 1位标识，由于long基本类型在Java中是带符号的，最高位是符号位，正数是0，负数是1，所以id一般是正数，最高位是0<br>
     * 41位时间截(毫秒级)，注意，41位时间截不是存储当前时间的时间截，而是存储时间截的差值（当前时间截 - 开始时间截)
     * 得到的值），这里的的开始时间截，一般是我们的id生成器开始使用的时间，由我们程序来指定的（如下下面程序IdWorker类的startTime属性）。
     * 41位的时间截，可以使用69年，年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
     * 10位的数据机器位，可以部署在1024个节点，包括5位datacenterId和5位workerId<br>
     * 12位序列，毫秒内的计数，12位的计数顺序号支持每个节点每毫秒(同一机器，同一时间截)产生4096个ID序号<br>
     * 加起来刚好64位，为一个Long型。<br>
     * SnowFlake的优点是，整体上按照时间自增排序，并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分)，
     * 并且效率较高，经测试，SnowFlake每秒能够产生26万ID左右。
     *
     * 由于没有datacenter，只需要按服务器ID区分即可，所以将datacenter和worker整合一起，支持1023个服务器，效果如下：
     * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 0000000000 - 000000000000 <br>
     */


    /**
     * 开始时间截(2019-01-01)，不能改变
     */
    private static final long epoch = 1546272000000L;
//    static {
//        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//        try {
//            Date date = format.parse("2019-01-01 00:00:00");
//            epoch = date.getTime();
//        } catch (ParseException e) {
//            e.printStackTrace();
//        }
//    }

    /**
     * 服务器id所占的位数
     */
    private static final long serverIdBits = 10L;

    /**
     * 支持的最大服务器id，结果是1023 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
     */
    private static final long maxServerId = ~(-1L << serverIdBits);

    /**
     * 序列在id中占的位数
     */
    private static final long sequenceBits = 12L;

    /**
     * 机器ID向左移12位
     */
    private static final long serverIdShift = sequenceBits;

    /**
     * 时间截向左移22位(10+12)
     */
    private static final long timestampLeftShift = sequenceBits + serverIdBits;

    /**
     * 生成序列的掩码，这里为4095 (0b111111111111=0xfff=4095)
     */
    private static final long sequenceMask = ~(-1L << sequenceBits);

    /**
     * 服务器ID(1~1023)
     */
    private long serverId;

    /**
     * 毫秒内序列(0~4095)
     */
    private long sequence = 0L;

    /**
     * 上次生成ID的时间截
     */
    private long lastTimestamp = -1L;

    public static IDGenerator newGenerator() {
//        return new IDGenerator(GameUtils.ServerId);
        return new IDGenerator(1);
    }

    /**
     * 构造函数
     *
     * @param serverId 服务器ID (1~1023)
     */
    private IDGenerator(long serverId) {
        if (serverId > maxServerId || serverId < 1) {
            throw new IllegalArgumentException(String.format("Server ID不能大于%d或小于1", maxServerId));
        }
        this.serverId = serverId;
    }

    /**
     * 获得下一个ID
     *
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();

        // 如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format("系统时钟回退了，%d毫秒内不能生成ID", lastTimestamp - timestamp));
        }

        // 如果是同一时间生成的，则进行毫秒内序列
        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                // 毫秒内序列溢出,阻塞到下一个毫秒,获得新的时间戳
                timestamp = tillNextMillis(lastTimestamp);
            }
        } else {
            // 时间戳改变，毫秒内序列重置
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - epoch) << timestampLeftShift)
                | (serverId << serverIdShift)
                | sequence;
    }

    /**
     * 阻塞到下一个毫秒，直到获得新的时间戳
     *
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    private long tillNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     *
     * @return 当前时间(毫秒)
     */
    private long timeGen() {
        return System.currentTimeMillis();
    }

    //==============================Test=============================================

    /**
     * 测试
     */
    public static void main(String[] args) {
        IDGenerator idGenerator = new IDGenerator(1023);
        for (int i = 0; i < 100; i++) {
            long id = idGenerator.nextId();
            System.out.println(id + " : " + Long.toBinaryString(id));
        }
    }

}
